////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation.  All rights reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gadgeteer
{
    using Gadgeteer.Modules;
    using Microsoft.SPOT;
    using Microsoft.SPOT.IO;
    using Microsoft.SPOT.Presentation;
    using Microsoft.SPOT.Presentation.Media;
    using System;
    using System.ComponentModel;
    using System.IO;
    using System.Reflection;

    // Note: In .NET Micro Framework, structs are 2nd class citizens. They are created on the heap and the struct behavior is emulated by the runtime.
    //       On the desktop .NET Framework, structs are placed on the stack and therefore can be much master than classes.

    /// <summary>
    /// Represents colors that can be used in your Gadgeteer application.
    /// </summary>
    public struct Color 
    {
        /// <summary>
        /// Gets or sets the red component of this color structure.
        /// </summary>
        public byte R;
        /// <summary>
        /// Gets or sets the green component of this color structure.
        /// </summary>
        public byte G;
        /// <summary>
        /// Gets or sets the blue component of this color structure.
        /// </summary>
        public byte B;

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="r">The red component of the color.</param>
        /// <param name="g">The green component of the color.</param>
        /// <param name="b">The blue component of the color.</param>
        public Color(byte r, byte g, byte b) 
        {
            R = r;
            G = g;
            B = b;
        }
        

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="spotcolor">The color.</param>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public Color(Microsoft.SPOT.Presentation.Media.Color spotcolor) 
        {
            R = ColorUtility.GetRValue(spotcolor);
            G = ColorUtility.GetGValue(spotcolor);
            B = ColorUtility.GetBValue(spotcolor);
        }

        /// <summary>Returns a new <see cref="Color"/> structure with the specified color components.</summary>
        /// <param name="R">The red component of the color.</param>
        /// <param name="G">The green component of the color.</param>
        /// <param name="B">The blue component of the color.</param>
        /// <returns>A new <see cref="Color"/> structure with the specified color components.</returns>
        public static Color FromRGB(byte R, byte G, byte B) 
        {
            return new Color(R, G, B);
        }

        /// <summary>
        /// Enables a <see cref="Color"/> structure to be implicitly cast as 
        /// a <see cref="T:Microsoft.SPOT.Presentation.Media.Color">Microsoft.SPOT.Presentation.Media.Color</see>
        /// enumeration.
        /// </summary>
        /// <param name="color">The <see cref="Color"/> to cast.</param>
        /// <returns>
        /// An implicit value from the 
        /// <see cref="T:Microsoft.SPOT.Presentation.Media.Color">Microsoft.SPOT.Presentation.Media.Color</see>
        /// enumeration.
        /// </returns>
        static public implicit operator Microsoft.SPOT.Presentation.Media.Color(Color color)
        {
            return ColorUtility.ColorFromRGB(color.R, color.G, color.B);
        }

        /// <summary>
        /// Enables a value from the <see cref="T:Microsoft.SPOT.Presentation.Media.Color">Microsoft.SPOT.Presentation.Media.Color</see>
        /// enumeration to be cast as a <see cref="Color"/> structure.
        /// </summary>
        /// <param name="spotcolor">A <see cref="T:Microsoft.SPOT.Presentation.Media.Color">Microsoft.SPOT.Presentation.Media.Color</see> value.</param>
        /// <returns>A <see cref="Color"/> structure.</returns>
        static public implicit operator Color(Microsoft.SPOT.Presentation.Media.Color spotcolor)
        {
            return new Color(spotcolor);
        }

        /// <summary>
        /// Gets the color Black.
        /// </summary>
        public static readonly Color Black = new Color(Colors.Black);
        /// <summary>
        /// Gets the color Blue.
        /// </summary>
        public static readonly Color Blue = new Color(Colors.Blue);
        /// <summary>
        /// Gets the color Brown.
        /// </summary>
        public static readonly Color Brown = new Color(Colors.Brown);
        /// <summary>
        /// Gets the color Cyan.
        /// </summary>
        public static readonly Color Cyan = new Color(Colors.Cyan);
        /// <summary>
        /// Gets the color Dark Gray.
        /// </summary>
        public static readonly Color DarkGray = new Color(Colors.DarkGray);
        /// <summary>
        /// Gets the color Gray.
        /// </summary>
        public static readonly Color Gray = new Color(Colors.Gray);
        /// <summary>
        /// Gets the color Green.
        /// </summary>
        public static readonly Color Green = new Color(Colors.Green);
        /// <summary>
        /// Gets the color Light Gray.
        /// </summary>
        public static readonly Color LightGray = new Color(Colors.LightGray);
        /// <summary>
        /// Gets the color Magenta.
        /// </summary>
        public static readonly Color Magenta = new Color(Colors.Magenta);
        /// <summary>
        /// Gets the color Orange.
        /// </summary>
        public static readonly Color Orange = new Color(Colors.Orange);
        /// <summary>
        /// Gets the color Purple.
        /// </summary>
        public static readonly Color Purple = new Color(Colors.Purple);
        /// <summary>
        /// Gets the color Red.
        /// </summary>
        public static readonly Color Red = new Color(Colors.Red);
        /// <summary>
        /// Gets the color White.
        /// </summary>
        public static readonly Color White = new Color(Colors.White);
        /// <summary>
        /// Gets the color Yellow.
        /// </summary>
        public static readonly Color Yellow = new Color(Colors.Yellow);

        /// <summary>
        /// Serves as a hash function for a particular type. 
        /// </summary>
        /// <returns>A hash code for the current object.</returns>
        public override int GetHashCode()
        {
            return ((int)R << 16) + ((int)G << 8) + ((int)B);
        }

        /// <summary>
        /// Determines whether a specified instance (object) of is equal to the current object of that class.
        /// </summary>
        /// <param name="obj">The object you want to compare with the current object.</param>
        /// <returns><b>true</b> if the specified object is equal to the current object; otherwise, <b>false</b>.</returns>
        public override bool Equals(object obj)
        {
            if (!(obj is Color)) return false;
            Color c = (Color)obj;
            return R == c.R && B == c.B && G == c.G;
        }

        /// <summary>
        /// Overloaded equality operator.
        /// </summary>
        /// <param name="lhs">Left operand.</param>
        /// <param name="rhs">Right operand.</param>
        /// <returns><b>true</b> if <paramref name="lhs"/> and <paramref name="rhs"/> are equal; otherwise, <b>false</b>.</returns>
        public static bool operator ==(Color lhs, Color rhs)
        {
            return lhs.R == rhs.R && lhs.B == rhs.B && lhs.G == rhs.G;
        }
        
        /// <summary>
        /// Overloaded inequality operator.
        /// </summary>
        /// <param name="lhs">Left operand.</param>
        /// <param name="rhs">Right operand.</param>
        /// <returns><b>true</b> if <paramref name="lhs"/> and <paramref name="rhs"/> are not equal; otherwise, <b>false</b>.</returns>
        public static bool operator !=(Color lhs, Color rhs)
        {
            return lhs.R != rhs.R || lhs.B != rhs.B || lhs.G != rhs.G;
        }
        
        /// <summary>
        /// Returns a string that represents the current instance of the <see cref="Color"/> class. 
        /// </summary>
        /// <returns>A string that represents the current object.</returns>
        /// <remarks>
        /// This method returns a human-readable text string that is culture-sensitive.
        /// </remarks>
        public override string ToString()
        {
            return "Color [" + R + "," + G + "," + B + "]";
        }
    }

    /// <summary>
    /// Represents a picture encoded in a JPEG, GIF or BMP format. 
    /// </summary>
    /// <remarks>
    /// <see cref="Picture"/> encapsulates a byte array containing the image data, 
    /// and provides a method to create a <see cref="Bitmap"/> from the data.
    /// A <see cref="Picture"/> object can be explicitly cast to a <see cref="T:Microsoft.SPOT.Bitmap"/>.
    /// </remarks>
    public class Picture
    {
        /// <summary>
        /// Represents the encoding of a <see cref="Picture"/> object.
        /// </summary>
        public enum PictureEncoding
        {
            /// <summary>
            /// Encoded in GIF format.
            /// </summary>
            GIF = 1,
            /// <summary>
            /// Encoded in JPEG format.
            /// </summary>
            JPEG = 2,
            /// <summary>
            /// Encoded in BMP format.
            /// </summary>
            BMP = 3
        }

        /// <summary>
        /// Gets the picture data as a byte array.
        /// </summary>
        public byte[] PictureData { get; private set; }

        /// <summary>
        /// Gets the picture encoding format.
        /// </summary>
        public PictureEncoding Encoding { get; private set; }

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="pictureData">The picture data as a byte array.</param>
        /// <param name="encoding">The picture encoding format.</param>
        public Picture(byte[] pictureData, PictureEncoding encoding)
        {
            PictureData = pictureData;
            Encoding = encoding;
        }

        /// <summary>
        /// Enables a <see cref="Picture"/> object to be explicitly cast as a <see cref="T:Microsoft.SPOT.Bitmap"/>.
        /// </summary>
        /// <param name="picture">A <see cref="Picture"/> object to convert into a <see cref="T:Microsoft.SPOT.Bitmap"/>.</param>
        /// <returns>A new <see cref="T:Microsoft.SPOT.Bitmap"/> object.</returns>
        /// <remarks>
        /// <note>This operator returns a new <see cref="Microsoft.SPOT.Bitmap"/> object.</note>
        /// </remarks>
        public static explicit operator Bitmap(Picture picture)
        {            
            return new Bitmap(picture.PictureData, (Bitmap.BitmapImageType) picture.Encoding);
        }

        /// <summary>
        /// Creates and returns a new <see cref="T:Microsoft.SPOT.Bitmap"/> object based on the picture data.
        /// </summary>
        /// <returns>The picture as an uncompressed <see cref="T:Microsoft.SPOT.Bitmap"/> object.</returns>
        public Bitmap MakeBitmap()
        {
            return (Bitmap)this;
        }
    }

    /// <summary>
    /// Encapsulates the <see cref="Volume"/> information and the <see cref="RootDirectory"/> of a storage device.  
    /// Provides wrappers for useful functions in System.IO.File and System.IO.Directory static classes, for ease of API discovery.
    /// </summary>
    public class StorageDevice
    {
        /// <summary>
        /// Gets the volume information associated with this <see cref="StorageDevice"/>.
        /// </summary>
        public readonly VolumeInfo Volume;


        private string[] RemoveRootDirectoryFromPaths(string[] filePaths)
        {
            if (filePaths == null)
                return null;

            for (int i = 0; i < filePaths.Length; i++)
            {
                int index = filePaths[i].IndexOf(RootDirectory + Path.DirectorySeparatorChar);
                if(index >= 0) filePaths[i] = filePaths[i].Substring(index + RootDirectory.Length + 1);
            }
            return filePaths;
        }

        /// <summary>
        /// Gets the root directory for this <see cref="StorageDevice"/>.
        /// </summary>
        public readonly string RootDirectory;

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="volumeInfo">The volume information for this storage device.</param>
        public StorageDevice(VolumeInfo volumeInfo)
        {
            Volume = volumeInfo;
            RootDirectory = Volume.RootDirectory;
        }

        /// <summary>
        /// Gets a list of files in the root directory of this <see cref="StorageDevice"/>.
        /// </summary>
        /// <returns>An array of file names.</returns>
        public string[] ListRootDirectoryFiles()
        {
            return RemoveRootDirectoryFromPaths(Directory.GetFiles(RootDirectory));
        }

        /// <summary>
        /// Gets a list of files in the specified path of this <see cref="StorageDevice"/>.
        /// </summary>
        /// <param name="path">The path to get the list of files for.</param>
        /// <returns>An array of file names.</returns>
        public string[] ListFiles(string path)
        {
            return RemoveRootDirectoryFromPaths(Directory.GetFiles(Path.Combine(RootDirectory, path)));
        }

        /// <summary>
        /// Gets a list of directories in the root directory of this <see cref="StorageDevice"/>.
        /// </summary>
        /// <returns>An array of directory names.</returns>
        public string[] ListRootDirectorySubdirectories()
        {
            return RemoveRootDirectoryFromPaths(Directory.GetDirectories(RootDirectory));
        }

        /// <summary>
        /// Gets a list of directories in the specified directory of this <see cref="StorageDevice"/>.
        /// </summary>
        /// <param name="path">The directory path to get a list of directories for, relative to the storage device root directory.</param>
        /// <returns>>An array of directory names.</returns>
        public string[] ListDirectories(string path)
        {
            return RemoveRootDirectoryFromPaths(Directory.GetDirectories(Path.Combine(RootDirectory, path)));
        }

        /// <summary>
        /// Gets an image object from the specified file.
        /// </summary>
        /// <param name="filePath">The path to file that contains the image, relative to the storage device root directory.</param>
        /// <param name="imageType">The type of image contained in the file.</param>
        /// <returns>The image.</returns>
        public Bitmap LoadBitmap(string filePath, Bitmap.BitmapImageType imageType)
        {
            return new Bitmap(File.ReadAllBytes(Path.Combine(RootDirectory, filePath)), imageType);
        }

        /// <summary>
        /// Creates a new file, writes the specified byte array to the file, and then closes the file. If the target file already exists, it is overwritten.
        /// </summary>
        /// <param name="filePath">The file to write to, relative to the storage device root directory.</param>
        /// <param name="fileData">The bytes to write to the file.</param>
        public void WriteFile(string filePath, byte[] fileData)
        {
            File.WriteAllBytes(Path.Combine(RootDirectory, filePath), fileData);
        }

        /// <summary>
        /// Opens a binary file, reads the contents of the file into a byte array, and then closes the file. 
        /// </summary>
        /// <param name="filePath">The file to open for reading, relative to the storage device root directory.</param>
        /// <returns>A byte array containing the contents of the file.</returns>
        public byte[] ReadFile(string filePath)
        {
            return File.ReadAllBytes(Path.Combine(RootDirectory, filePath));
        }

        /// <summary>
        /// Opens a FileStream on the specified path, having the specified mode with read, write, or read/write access. 
        /// </summary>
        /// <param name="filePath">The file to open, relative to the storage device root directory.</param>
        /// <param name="mode">
        ///  A <see cref="T:System.IO.FileMode"/> value that specifies whether a file is created if one does not exist, 
        ///  and determines whether the contents of existing files are retained or overwritten.
        /// </param>
        /// <param name="access">A <see cref="T:System.IO.FileAccess"/> value that specifies the operations that can be performed on the file.</param>
        /// <returns>A FileStream on the specified path, having the specified mode with read, write, or read/write access.</returns>
        public FileStream Open(string filePath, FileMode mode, FileAccess access)
        {
            return File.Open(Path.Combine(RootDirectory, filePath), mode, access);
        }

        /// <summary>
        /// Opens an existing file for reading.
        /// </summary>
        /// <param name="filePath">The file to be opened for reading, relative to the storage device root directory.</param>
        /// <returns>A read-only FileStream on the specified path.</returns>
        public FileStream OpenRead(string filePath)
        {
            return File.OpenRead(Path.Combine(RootDirectory, filePath));
        }

        /// <summary>
        /// Opens an existing file for writing.
        /// </summary>
        /// <param name="filePath">The file to be opened for writing, relative to the storage device root directory.</param>
        /// <returns>A read/write FileStream on the specified path.</returns>
        public FileStream OpenWrite(string filePath)
        {
            return File.OpenWrite(Path.Combine(RootDirectory, filePath));
        }

        /// <summary>
        /// Delete a file under this storage device.
        /// </summary>
        /// <param name="filePath">The path to the file to delete, relative to the storage device root directory.</param>
        public void Delete(string filePath)
        {
            File.Delete(Path.Combine(RootDirectory, filePath));
        }

        /// <summary>
        /// Makes a subdirectory under this storage device.
        /// </summary>
        /// <param name="directoryPath">The path to the directory to create, relative to the storage device root directory.</param>
        public void CreateDirectory(string directoryPath)
        {
            Directory.CreateDirectory(Path.Combine(RootDirectory, directoryPath));
        }

        /// <summary>
        /// Delete a directory under this storage device.
        /// </summary>
        /// <param name="dirPath">The path to the directory to delete, relative to the storage device root directory.</param>
        /// <param name="recursive">Whether the deletion is recursive (optional, default false)</param>
        public void DeleteDirectory(string dirPath, bool recursive = false)
        {
            Directory.Delete(Path.Combine(RootDirectory, dirPath), recursive);
        }

    }   

}

